﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using KegData;
using KegCommunication;

namespace KegMonitor
{
    public class KegWriter
    {
        static string ConnectionString;
        
        // Lock objects ensure that we do not cause more than one temperature to be written at a time
        static object temperatureLock = new Object();
        static object pourLock = new Object();

        public static KegeratorDataContext DataSource
        {
            get
            {
                KegeratorDataContext kdc = new KegeratorDataContext(ConnectionString);
                kdc.DeferredLoadingEnabled = true;
                return kdc;
            }
        }

        // Current user ID keeps track of the user set by the 
        static int currentUserId = -1;

        public static void Initialize(String connection)
        {
            ConnectionString = connection;
            currentUserId = GetDefaultUserId();
        }

        public static void OnPour(object sender, PourEventArgs e)
        {
            lock (pourLock)
            {
                if (e != null)
                {
                    // Update the keg volume
                    UpdateKeg(e.Tap, e.Volume);

                    // Record the pour to the database
                    Pour p = RecordPour(e.Tap, e.Volume, e.Time);

                    // Call the callback on the reader with the new pour
                    KegReader reader = sender as KegReader;
                    if (reader != null)
                    {
                        reader.OnPourPlugin(p);
                    }
                }
            }
        }
            
        public static void OnTemperature(object sender, TemperatureEventArgs e)
        {
            lock (temperatureLock)
            {
                if (e != null)
                {
                    // Record this temperature
                    Temperature t = RecordTemperature(e.Address, e.Temperature, e.Time);

                    // Store this value as the latest for the location
                    // Call the callback on the reader with the new temperature
                    KegReader reader = sender as KegReader;
                    if (reader != null)
                    {
                        reader.OnTemperaturePlugin(t);
                    }
                }
            }
        }

        public static Pour RecordPour(byte Position, double Volume)
        {
            return RecordPour(Position, Volume, DateTime.Now);
        }

        public static Pour RecordPour(byte Position, double Volume, DateTime Time)
        {
            Pour retPour = null;

            KegeratorDataContext dataContext = DataSource;
            
            // Get the current keg by tap id (tap is unique).
            Keg currentKeg = GetCurrentKeg(Position, dataContext);

            User currentUser = GetCurrentUser(dataContext);

            if ((currentKeg != null) && (currentUser != null))
            {
                // Create the pour object.
                Pour Pour = new Pour();
                Pour.Keg = currentKeg;
                Pour.User = currentUser;
                Pour.Time = Time;
                Pour.Volume = Volume;
                currentKeg.LastPour = null;

                currentUser.TotalVolumePoured += Volume;
                dataContext.Pours.InsertOnSubmit(Pour);
                dataContext.SubmitChanges();

                // TODO: Add a reference to this pour to the keg (uncomment this line)
                //currentKeg.LastPour = Pour;
                //monitor.SubmitChanges();

                KegLog.LogData(String.Format("Recorded {0}L of {1} (tap {2}) at {3} by {4}",
                    Pour.Volume,
                    Pour.Keg.Beer.Name,
                    Position,
                    Pour.Time,
                    Pour.User.Name));

                retPour = Pour;
            }

            // Set the current user back to the default user (clear it)
            currentUserId = GetDefaultUser(dataContext).Id;

            dataContext.Connection.Close();
            return retPour;
        }

        public static bool UpdateKeg(byte Position, double Volume)
        {
            bool ret = false;

            KegeratorDataContext dataContext = DataSource;

            // Get the current keg by tap id (tap is unique).
            Keg currentKeg = GetCurrentKeg(Position, dataContext);

            if (currentKeg != null)
            {
                // Update the volume
                currentKeg.CurrentVolume -= Volume;

                // Submit the changes
                dataContext.SubmitChanges();

                ret = true;
            }

            dataContext.Connection.Close();
            return ret;
        }

        private static Keg GetCurrentKeg(byte Position, KegeratorDataContext dataContext)
        {
            DateTime now = DateTime.Now;
            bool SubmitChanges = false;

            // Get the current tap (by position)
            Tap currentTap = (from t in dataContext.Taps
                              where t.Position == Position
                              select t).FirstOrDefault();
            if (currentTap == null)
            {
                currentTap = new Tap();
                currentTap.Position = Position;

                // Insert the new tap
                dataContext.Taps.InsertOnSubmit(currentTap);
                SubmitChanges = true;
            }
            
            Keg currentKeg = currentTap.Keg;

            if (currentKeg == null)
            {
                currentKeg = new Keg();
                currentKeg.Filled = now;
                currentTap.Keg = currentKeg;

                // Insert the new keg
                if (!SubmitChanges)
                {
                    dataContext.Kegs.InsertOnSubmit(currentKeg);
                    SubmitChanges = true;
                }
            }

            Beer currentBeer = currentKeg.Beer;

            if (currentBeer == null)
            {
                currentBeer = new Beer();
                currentBeer.Name = "Unknown Beer";
                currentBeer.Description = "Automatically generated on " + now + " by the kegmonitor when a pour was initiated on tap " + Position + ".";
                currentKeg.Beer = currentBeer;

                // Insert the new beer
                if (!SubmitChanges)
                {
                    dataContext.Beers.InsertOnSubmit(currentBeer);
                    SubmitChanges = true;
                }
            }

            if (SubmitChanges)
            {
                dataContext.SubmitChanges();
            }

            return currentKeg;
        }

        public static bool UpdateLatestTemperature(String Address, double Value, DateTime Time)
        {
            KegeratorDataContext dataContext = DataSource;

            // Get the temperature location based on the address
            TemperatureLocation currentLocation = GetTemperatureLocation(Address, dataContext);

            LatestTemperature latestTemp = (from t in dataContext.LatestTemperatures
                                            where t.TemperatureLocation == currentLocation
                                            select t).FirstOrDefault();
            if (latestTemp == null)
            {
                latestTemp = new LatestTemperature();
                dataContext.LatestTemperatures.InsertOnSubmit(latestTemp);
            }

            latestTemp.Time = Time;
            latestTemp.Value = Value;
            latestTemp.TemperatureLocation = currentLocation;

            dataContext.SubmitChanges();

            //KegLog.LogData(String.Format("Updating {0}*C for '{1}' at {2}",
            //                   latestTemp.Value,
            //                   latestTemp.TemperatureLocation.Name,
            //                   latestTemp.Time));

            dataContext.Connection.Close();
            return true;
        }

        public static Temperature RecordTemperature(String Address, double Value)
        {
            return RecordTemperature(Address, Value, DateTime.Now);
        }

        public static Temperature RecordTemperature(String Address, double Value, DateTime Time)
        {
            Temperature retTemp = null;

            KegeratorDataContext dataContext = DataSource;

            TemperatureLocation currentLocation = GetTemperatureLocation(Address, dataContext);

            // Get the temperature location based on the address
            if (currentLocation != null)
            {
                // Calculate the earliest that the last entry could have been entered for this to be new.
                TimeSpan delay = new TimeSpan(0, 0, 0, 0, Properties.Settings.Default.TemperatureDelay);
                DateTime pastEntry = Time.Subtract(delay);

                Temperature latestTemp = (from t in dataContext.Temperatures
                                          orderby t.Time descending
                                          where t.TemperatureLocation == currentLocation
                                          select t).FirstOrDefault();


                if (latestTemp == null || latestTemp.Time <= pastEntry)
                {
                    // A long enough duration has passed to require a new entry
                    Temperature Temperature = new Temperature();
                    Temperature.TemperatureLocation = currentLocation;
                    Temperature.Value = Value;
                    Temperature.Time = Time;

                    dataContext.Temperatures.InsertOnSubmit(Temperature);
                    dataContext.SubmitChanges();

                    KegLog.LogData(String.Format("Recorded {0}*F for '{1}' at {2}",
                                                    Temperature.Value,
                                                    Temperature.TemperatureLocation.Name,
                                                    Temperature.Time));

                    retTemp = Temperature;
                }
            }

            dataContext.Connection.Close();
            return retTemp;
        }

        public static TemperatureLocation GetTemperatureLocation(String Address, KegeratorDataContext dataContext)
        {
            TemperatureLocation currentLocation = (from tl in dataContext.TemperatureLocations
                                                   where tl.OneWireId == Address
                                                   select tl).FirstOrDefault();

            if (currentLocation == null)
            {
                // An entry for the current OneWire address does not exist in the table
                // Create a new one with a generic name.
                KegLog.LogComment("Location '" + Address + "' does not exist, creating it.");
                currentLocation = new TemperatureLocation();
                currentLocation.OneWireId = Address;
                currentLocation.Name = "Unknown (" + Address + ")";

                // Add this location to the data
                dataContext.TemperatureLocations.InsertOnSubmit(currentLocation);
                dataContext.SubmitChanges();
            }

            return currentLocation;
        }

        private static User GetCurrentUser(KegeratorDataContext dataContext)
        {
            // Get the user associated with the current user id
            User user = (from u in dataContext.Users
                         where u.Id == currentUserId
                         select u).FirstOrDefault();

            if (user == null)
            {
                user = GetDefaultUser(dataContext);
            }

            return user;
        }

        public static void OnUserAuthenticated(object sender, UserAuthenticatedEventArgs e)
        {
            currentUserId = e.Id;
        }

        public static User GetDefaultUser(KegeratorDataContext dataContext)
        {
            // Returns the user marked as default
            User user = (from u in dataContext.Users
                         where u.Id == Properties.Settings.Default.DefaultUserId
                         select u).FirstOrDefault();

            if (user == null)
            {
                user = new User();
                user.Name = "Unknown";
                // TODO: Other user values they may be important
                dataContext.Users.InsertOnSubmit(user);
                dataContext.SubmitChanges();

                // Save this user as the new default
                Properties.Settings.Default.DefaultUserId = user.Id;
                Properties.Settings.Default.Save();
            }

            return user;
        }

        public static int GetDefaultUserId()
        {
            int retId = -1;

            KegeratorDataContext dataContext = DataSource;
            User currentUser = GetDefaultUser(dataContext);

            if (currentUser != null)
            {
                retId = currentUser.Id;
            }

            dataContext.Connection.Close();
            return retId;
        }

    }
}
